package no.gnet.edvd.cron;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.sql.DataSource;

import no.gnet.edvd.DataStore;
import no.gnet.edvd.cron.types.DoneBet;
import no.gnet.edvd.cron.types.LosingBet;
import no.gnet.edvd.cron.types.WinningBet;
import no.gnet.edvd.database.mysql.MySQLDatastore;
import no.gnet.edvd.engines.Engine;
import no.gnet.edvd.engines.EventOdds;
import no.gnet.edvd.statemachine.BetState;
import no.gnet.edvd.statemachine.Context;
import no.gnet.edvd.types.ActivityType;
import no.gnet.edvd.types.Bet;
import no.gnet.edvd.types.Event;
import no.gnet.edvd.types.Option;
import no.gnet.edvd.types.Player;
import no.gnet.edvd.types.Transfer;
import no.gnet.edvd.util.Mailer;

import org.apache.log4j.Logger;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;

import com.mysql.jdbc.Driver;

public class StateJob {

	Logger logger = Logger.getLogger(StateJob.class);

	Player systemplayer = null;

	DataStore datastore;

	Engine engine;

	Mailer mailer;

	public void execute() throws Exception {
		processOpenEvents();
		processEventsClosedForBetting();
		settleFinishedEvents();
		historizeSettledEvents();
	}
	
	public DataSource dataSource() throws Exception {
        SimpleDriverDataSource simpleDriverDataSource = new SimpleDriverDataSource();

        Class<? extends Driver> d = (Class<? extends Driver>) Class.forName("com.mysql.jdbc.Driver");

        String user = "edvd",
                pw = "mercedes",
                url = "jdbc:mysql://localhost:3306/edvd_test";

        simpleDriverDataSource.setDriverClass(d);
        simpleDriverDataSource.setUsername(user);
        simpleDriverDataSource.setPassword(pw);
        simpleDriverDataSource.setUrl(url);
        return simpleDriverDataSource;
    }

	private void historizeSettledEvents() {
		// TODO Auto-generated method stub

	}

	private void settleFinishedEvents() {
		List<Event> decidedEvents = getEventsWithState(BetState.EVENT_DECIDED);
		if (!decidedEvents.isEmpty()) {
			logger.info("Candidates for state transitions : "
					+ decidedEvents.size() + " instances, with state "
					+ BetState.EVENT_DECIDED);
			_handleDecidedEvents(decidedEvents);
		} else {
			logger.info("No candidates for transitioning to "
					+ BetState.EVENT_DECIDED.nextState());
		}
	}

	private void processEventsClosedForBetting() {
		List<Event> eventsClosedForBetting = getEventsWithState(BetState.BETTING_CLOSED);
		if (!eventsClosedForBetting.isEmpty()) {
			logger.info("Candidates for state transitions : "
					+ eventsClosedForBetting.size() + " instances, with state "
					+ BetState.BETTING_CLOSED);
			_handleEventsClosedForBetting(eventsClosedForBetting);
		}
	}

	private void processOpenEvents() {
		List<Event> eventsOpenForBetting = getEventsWithState(BetState.OPEN_FOR_BETTING);
		if (!eventsOpenForBetting.isEmpty()) {
			logger.info("Candidates for state transitions : "
					+ eventsOpenForBetting.size() + " instances, with state "
					+ BetState.OPEN_FOR_BETTING);
			_handleOpenBets(eventsOpenForBetting);
		}
	}

	private void _handleDecidedEvents(List<Event> decidedEvents) {
		for (Event event : decidedEvents) {
			Option winner = datastore.getWinnerForEvent(event);
			if (winner == null) {
				logger.debug("Ignoring decided event, no winner picked for event: "
						+ event);
			} else {
				logger.debug("Winner picked, presumably ready for transition: "
						+ event);
				if (!BetState.EVENT_DECIDED
						.readyToTransition(new Context(event))) {
					throw new IllegalStateException("Not ready to transition: "
							+ event);
				}
				_settleEvent(event, winner);
				_transitionEventToSettled(event);
			}
		}
	}

	private void _settleEvent(Event event, Option winner) {
		List<Bet> bets = datastore.getBetsForEvent(event);
		EventOdds eodds = engine.getOddsForEvent(event, datastore);
		double odds = eodds.get(winner);
		
		System.out.println("Odds: " + odds);
		
		List<WinningBet> winnerbets = new ArrayList<WinningBet>();
		List<LosingBet> loserbets = new ArrayList<LosingBet>();

		split(winner, bets, winnerbets, loserbets, odds);
		setWins(winnerbets, odds);
		List<Transfer> transferlist = partage(winnerbets, loserbets);
		for (Iterator<Transfer> iterator = transferlist.iterator(); iterator
				.hasNext();) {
			Transfer transfer = (Transfer) iterator.next();
			logger.info("Transfer : " + transfer);
			System.out.println("Transfer : " + transfer);
			if(transfer.getFrom().email.equals(transfer.getTo().email)){
				logger.warn("We've got a self-transfer, we ignore it : " + transfer);
				System.out.println("We've got a self-transfer, we ignore it : " + transfer);
			} else {
				datastore.storeTransfer(transfer);
			}
		}
	}

	private void setWins(List<WinningBet> winners, double odds) {
		for (WinningBet winner : winners) {
			winner.setProfit(winner.getAmount() * odds);
		}
	}

	private List<Transfer> partage(List<WinningBet> winnerbets,
			List<LosingBet> loserbets) {
		List<Transfer> transferlist = new ArrayList<Transfer>();
		for (DoneBet loserbet : loserbets) {
			handleLoserBet(winnerbets, transferlist, loserbet);
		}
		return transferlist;
	}

	private void handleLoserBet(List<WinningBet> winnerbets,
			List<Transfer> transferlist, DoneBet loserbet) {
		for (WinningBet winningbet : winnerbets) {
			double losingAmount = loserbet.remaining();
			double due = winningbet.due();
			if (due > 0.0) {
				if (losingAmount >= due) {
					createTransfer(loserbet, transferlist, due, winningbet);
				} else if (losingAmount > 0) {
					createTransfer(loserbet, transferlist, losingAmount,
							winningbet);
				}
			}
		}
	}

	private double roundDown(double odds) {
		return Math.floor((100 * odds)) / 100;
	}

	private void createTransfer(DoneBet loserbet, List<Transfer> transfers,
			double amount, WinningBet winningbet) {
		Transfer t = new Transfer(loserbet.owner, winningbet.owner,
				roundDown(amount));
		t.setBetid(loserbet.getBet().id);
		loserbet.handle(amount);
		winningbet.handle(amount);
		transfers.add(t);
	}

	private void split(Option winner, List<Bet> bets,
			List<WinningBet> winnerbets, List<LosingBet> loserbets, double odds) {
		for (Bet bet : bets) {
			if (bet.getOption().equals(winner)) {
				winnerbets.add(new WinningBet(bet));
			} else {
				loserbets.add(new LosingBet(bet));
			}
		}
	}

	private void _handleWinnerP2PWay(Event event, EventOdds eodds, double odds,
			Bet bet, String email) {
		Player p = bet.getPlayer();
		Option winningOption = bet.getEvent().getWinningOption();
		boolean winner = winningOption.getName().equals(
				bet.getOption().getName());
		double amount_won = odds * bet.amount;

	}

	private void _handleLoserOldway(Event event, String email) {
		logger.info("Sending info-message to loser, " + email);
		mailer.sendMail("EddeVedde", email, "Losing bet",
				_losingBetMessage(event));
	}

	private void _handleWinnerOldWay(Event event, EventOdds eodds, double odds,
			Bet bet, String email) {
		logger.info("Sending info-message to winner, " + email);
		bet.event = event;
		double payout = datastore.settleBet(bet, eodds);
		mailer.sendMail("EddeVedde", email, "Winning bet",
				_winningBetMessage(event, odds, bet, payout));
	}

	private String _winningBetMessage(Event event, double odds, Bet bet,
			double payout) {
		return "The event named "
				+ event.getName()
				+ " was just decided, and your bet matches the outcome of the event! Your bet was "
				+ bet.amount
				+ " and the final odds for the winning option was " + odds
				+ " Your account " + "has been updated win the amount: "
				+ payout;
	}

	private String _losingBetMessage(Event event) {
		return "The event named "
				+ event.getName()
				+ " was just decided, and your bet unfortunately does not match the outcome of the event";
	}

	private void _transitionEventToSettled(Event event) {
		datastore.transitionTo(event, BetState.SETTLED);
		datastore.logActivity(
				ActivityType.EVENT_TRANSITION,
				describeTransition(event, BetState.EVENT_DECIDED,
						BetState.SETTLED), getSystemUser());		
	}

	/**
	 * The only criteria here is that the event time has passed/event has been
	 * decided
	 * */
	private void _handleEventsClosedForBetting(
			List<Event> eventsClosedForBetting) {
		for (Event event : eventsClosedForBetting) {
			if (!BetState.BETTING_CLOSED.readyToTransition(new Context(event))) {
				throw new IllegalStateException("Not ready to transition: "
						+ event);
			}
		}
		List<Event> realList = datastore.betsEventTimePassed();
		// now, transition those to next state
		if (!realList.isEmpty()) {
			logger.info("About to transition " + realList.size()
					+ " events to  " + BetState.EVENT_DECIDED);
			datastore.eventTimePassed(realList);
			for (Event event : realList) {
				datastore.logActivity(
						ActivityType.EVENT_TRANSITION,
						describeTransition(event, BetState.BETTING_CLOSED,
								BetState.EVENT_DECIDED), getSystemUser());
				if (event.owner.id != getSystemUser().id) {
					informOwnerBetTimeHasPassed(event);
				}
			}
		}
	}

	private void informOwnerBetTimeHasPassed(Event event) {
		List<Bet> betsForEvent = datastore.getBetsForEvent(event);
		mailer.sendMail("EddeVedde", event.owner.email, "How did it go?",
				_timeToSetWinner(event, betsForEvent));
		if (betsForEvent == null || betsForEvent.isEmpty()) {
			mailer.sendMail("EddeVedde", event.owner.email,
					"No bets were placed on your event",
					"No need to set winner, there were no bets on your event");
		}
	}

	private String _timeToSetWinner(Event event, List<Bet> betsForEvent) {
		return "Winner needs to be set - you are the creator of the event "
				+ event.getName()
				+ ", now the time has passed and its time to set the winner. Follow the link below to decide.";
	}

	private String describeTransition(Event event, BetState fromstate,
			BetState tostate) {
		return "Event " + event.name + "/" + event.id + " transitioned from "
				+ fromstate.name() + " to " + tostate.name();
	}

	/**
	 * The only criteria here is that the betting deadline has passed
	 * */
	private void _handleOpenBets(List<Event> eventsOpenForBetting) {
		for (Event event : eventsOpenForBetting) {
			if (!BetState.OPEN_FOR_BETTING
					.readyToTransition(new Context(event))) {
				throw new IllegalStateException("Not ready to transition: "
						+ event);
			}
		}
		List<Event> realList = datastore.betsToClose();
		// now, close those for betting
		if (!realList.isEmpty()) {
			logger.info("About to transition " + realList.size()
					+ " events to  " + BetState.BETTING_CLOSED);
			datastore.betsClosed(realList);
			for (Event event : realList) {
				datastore.logActivity(
						ActivityType.EVENT_TRANSITION,
						describeTransition(event, BetState.OPEN_FOR_BETTING,
								BetState.BETTING_CLOSED), getSystemUser());
			}
		}
	}

	private Player getSystemUser() {
		if (systemplayer == null) {
			Player player = Player.getSystemUser();
			if (!datastore.playerExists(player)) {
				systemplayer = datastore.storePlayer(player);
			} else {
				systemplayer = datastore.getPlayer(player);
			}
		}
		return systemplayer;
	}

	private List<Event> getEventsWithState(BetState state) {
		return datastore.getEventsInState(state);
	}

	public void setDatastore(DataStore datastore) {
		this.datastore = datastore;
	}

	public Mailer getMailer() {
		return mailer;
	}

	public void setMailer(Mailer mailer) {
		this.mailer = mailer;
	}

	public Engine getEngine() {
		return engine;
	}

	public void setEngine(Engine engine) {
		this.engine = engine;
	}

}
